<?php

declare(strict_types=1);

namespace App\Service;

use Hyperf\Redis\Redis;
use Hyperf\Di\Annotation\Inject;
use App\Exception\BusinessException;
use Hyperf\DbConnection\Db;

class SignInService
{
    #[Inject]
    protected Redis $redis;
    
    private const SIGNIN_BITMAP_PREFIX = 'signin:bitmap:';
    private const SIGNIN_GLOBAL_PREFIX = 'signin:global:';
    
    /**
     * 用户签到
     */
    public function signIn(int $userId): array
    {
        $monthKey = $this->getMonthKey($userId);
        $dayOffset = (int) date('j') - 1;
        $globalKey = $this->getGlobalKey();
        
        // 检查今日是否已签到
        if ($this->redis->getBit($monthKey, $dayOffset)) {
            throw new BusinessException('今日已签到', 1001);
        }
        
        // 使用管道执行原子操作
        $pipe = $this->redis->pipeline();
        $pipe->setBit($monthKey, $dayOffset, true);
        $pipe->setBit($globalKey, $userId, true);
        $pipe->expire($monthKey, 60 * 60 * 24 * 60); // 保留2个月
        $pipe->expire($globalKey, 60 * 60 * 24); // 当日排行榜保留1天
        $pipe->exec();
        
        // 计算签到数据
        $continuousDays = $this->getContinuousDays($userId);
        $totalDays = $this->getTotalSignInDays($userId);
        $points = $this->calculatePoints($continuousDays);
        
        // 记录签到日志
        $this->recordSignInLog($userId, $points);
        
        return [
            'continuous_days' => $continuousDays,
            'total_days' => $totalDays,
            'points_earned' => $points,
            'signed_at' => date('Y-m-d H:i:s')
        ];
    }
    
    /**
     * 获取连续签到天数
     */
    public function getContinuousDays(int $userId): int
    {
        $currentMonth = date('Ym');
        $currentKey = $this->getMonthKey($userId, $currentMonth);
        $dayOffset = (int) date('j') - 1;
        
        $continuous = 0;
        
        // 当前月向前追溯
        for ($i = $dayOffset; $i >= 0; $i--) {
            if ($this->redis->getBit($currentKey, $i)) {
                $continuous++;
            } else {
                break;
            }
        }
        
        // 如果当前月全勤，检查上月连续情况
        if ($continuous === ($dayOffset + 1)) {
            $lastMonth = date('Ym', strtotime('last month'));
            $lastKey = $this->getMonthKey($userId, $lastMonth);
            $lastMonthDays = (int) date('t', strtotime('last month'));
            
            $continuous += $this->calculateLastMonthContinuous($lastKey, $lastMonthDays);
        }
        
        return $continuous;
    }
    
    /**
     * 获取总签到天数
     */
    public function getTotalSignInDays(int $userId): int
    {
        $pattern = self::SIGNIN_BITMAP_PREFIX . $userId . ':*';
        $keys = $this->redis->keys($pattern);
        $total = 0;
        
        foreach ($keys as $key) {
            $total += $this->redis->bitCount($key);
        }
        
        return $total;
    }
    
    /**
     * 获取某天是否签到
     */
    public function getDayIsSign(int $userId, string $date=''): bool
    {
        $date = $date ?: date('Y-m-d');
        $yearMonth = substr($date, 0, 7);
        $key = str_replace('-','',$this->getMonthKey($userId, $yearMonth));

        $dayOffset = (int)date('j',strtotime($date)) - 1;

        return (bool) $this->redis->getBit($key, $dayOffset);
    }
    
    /**
     * 获取月度签到数据
     */
    public function getMonthSignInData(int $userId, string $yearMonth): array
    {
        $key = str_replace('-','',$this->getMonthKey($userId, $yearMonth));
        $daysInMonth = (int) date('t', strtotime($yearMonth . '-01'));
        $dayOffset = (int) date('j') - 1;

        $signInData = [];
        for ($i = 0; $i < $daysInMonth; $i++) {
            $isSign = (bool) $this->redis->getBit($key, $i);

            $status = 'unsigned';
            if ($isSign) {
                $status = 'signed';
            } elseif($i<$dayOffset) {
                $status = 'missed';
            }

            $signInData[$i + 1] = $status;
        }
        
        return $signInData;
    }
    
    /**
     * 获取当日签到排行榜
     */
    public function getTodayRank(int $limit = 100): array
    {
        $globalKey = $this->getGlobalKey();
        $userIds = [];

        // 统计当日总签到位数，用于快速判空和提前停止
        $bitCount = $this->redis->bitCount($globalKey);
        if ($bitCount === 0) {
            return [];
        }

        // 读取整个位图并在内存中逐位扫描
        $bitmap = $this->redis->getRange($globalKey, 0, -1);
        if ($bitmap === false || $bitmap === null || $bitmap === '') {
            return [];
        }

        $bytesLength = strlen($bitmap);
        $found = 0;

        for ($byteIndex = 0; $byteIndex < $bytesLength; $byteIndex++) {
            $byte = ord($bitmap[$byteIndex]);

            // 跳过全 0 的字节
            if ($byte === 0) {
                continue;
            }

            // 在一个字节内从高位到低位扫描（Redis 位序：bit0 为最高位）
            for ($bitInByte = 0; $bitInByte < 8; $bitInByte++) {
                $mask = 1 << (7 - $bitInByte);
                if (($byte & $mask) !== 0) {
                    $bitPosition = $byteIndex * 8 + $bitInByte; // 位偏移即用户ID
                    $userIds[] = $bitPosition;
                    $found++;

                    if ($found >= $limit || $found >= $bitCount) {
                        return $userIds;
                    }
                }
            }
        }

        return $userIds;
    }
    
    private function getMonthKey(int $userId, ?string $month = null): string
    {
        $month = $month ?? date('Ym');
        return self::SIGNIN_BITMAP_PREFIX . $userId . ':' . $month;
    }
    
    private function getGlobalKey(?string $date = null): string
    {
        $date = $date ?? date('Ymd');
        return self::SIGNIN_GLOBAL_PREFIX . $date;
    }
    
    private function calculateLastMonthContinuous(string $key, int $daysInMonth): int
    {
        $continuous = 0;
        for ($i = $daysInMonth - 1; $i >= 0; $i--) {
            if ($this->redis->getBit($key, $i)) {
                $continuous++;
            } else {
                break;
            }
        }
        return $continuous;
    }
    
    private function calculatePoints(int $continuousDays): int
    {
        $basePoints = 10;
        $continuousBonus = min($continuousDays * 2, 50); // 连续奖励最多50分
        
        return $basePoints + $continuousBonus;
    }
    
    private function recordSignInLog(int $userId, int $points): void
    {
        // 记录到MySQL用于数据持久化
        DB::table('user_signin_logs')->insert([
            'user_id' => $userId,
            'points_earned' => $points,
            'signin_date' => date('Y-m-d'),
            'created_at' => date('Y-m-d H:i:s')
        ]);
        
        // 更新用户积分
        DB::table('users')
            ->where('id', $userId)
            ->increment('points', $points);
    }
}